﻿/*
    MAST -- A file Merging and Splitting Tool
    Copyright (C) 2011 Harry Rose

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;

namespace FileMergeAndSplitTool
{
    /// <summary>
    /// Called when a merge operation makes progress.
    /// </summary>
    /// <param name="mergeableFile">The file being merged</param>
    /// <param name="currentPart">The current part that is being merged</param>
    /// <param name="percentComplete">How complete the operation is</param>
    public delegate void MergeProgress(MergeableFile mergeableFile, string currentPart, double percentComplete);

    /// <summary>
    /// Represents a set of file parts that are to be merged into a larger file.
    /// </summary>
    public class MergeableFile : MAST_File
    {
        #region PrivateInstanceVariables
        const int bufferSizeBytes = 1024 * 1024 * 10;
        public event MergeProgress ProgressUpdate;

        public string outputFilename = "";
        List<FilePart> parts = new List<FilePart>();
        Object partsLock = new Object();
        bool partsSorted = false;
        #endregion

        #region Properties
        /// <summary>
        /// The filename of the merged file
        /// </summary>
        public string OutputFilename
        {
            get { return outputFilename; }
        }

        /// <summary>
        /// The file parts that exist on the filesystem
        /// </summary>
        public List<FilePart> Parts
        {
            get { lock (partsLock) { if (!partsSorted) { SortParts(); } return parts; } }
        }

        #endregion

        #region Constructors
        /// <summary>
        ///     Create a MergeableFile, which will be merged to a file with a specific outputFilename (and directory)
        /// </summary>
        /// <param name="outputFilename">Filename (including directory) of the merged file</param>
        public MergeableFile(string outputFilename) :
            base(outputFilename)
        {
            this.outputFilename = outputFilename;
        }

        /// <summary>
        ///     Create a MergeableFile, which will be merged to a file with a specific outputFilename (and directory), and contains specific parts.
        /// </summary>
        /// <param name="outputFilename">Filename (including directory) of the merged file</param>
        /// <param name="parts">A list of filenames representing the parts of the file</param>
        public MergeableFile(string outputFilename, List<string> parts)
            :this (outputFilename)
        {
            foreach(string fn in parts)
            {
                AddPart(fn);
            }
        }
        #endregion

        /// <summary>
        /// Sorts the parts in order of increasing part number.
        /// </summary>
        public void SortParts()
        {
            lock (partsLock)
            {
                if (partsSorted) return;
                parts.Sort(delegate(FilePart p1, FilePart p2) { return p1.Part.CompareTo(p2.Part); });
                partsSorted = true;
            }
        }

        /// <summary>
        /// Adds a part to the list of fileparts of this file to be merged
        /// </summary>
        /// <param name="filename">Filename (including path) of the part file</param>
        /// <param name="partNo">The part number of the merged file that part file represents</param>
        public void AddPart(string filename, int partNo)
        {
            lock (partsLock)
            {
                parts.Add(new FilePart(filename, partNo));
                partsSorted = false;
            }
        }

        /// <summary>
        /// Adds a part to the list of fileparts of this file to be merged.  Part number automatically increases.
        /// </summary>
        /// <param name="filename">Filename (including path) of the part file</param>
        public void AddPart(string filename)
        {
            AddPart(filename,parts.Count);
        }

        /// <summary>
        /// Merge the parts of this file into a file called outputFilename
        /// </summary>
        /// <param name="outputFilename">Filename of the merged file</param>
        public void Merge(string outputFilename)
        {
            BinaryWriter writer = new BinaryWriter(new FileStream(outputFilename, FileMode.Create));
            byte[] buffer = new byte[bufferSizeBytes];

            if (!partsSorted)
            {
                SortParts();
            }
            int fileCnt = 0;
            foreach (FilePart part in parts)
            {
                ProgressUpdate(this, part.Name, 100.0 * (double)fileCnt / (double)parts.Count);

                BinaryReader reader = new BinaryReader(new FileStream(part.FullName, FileMode.Open));
                int offset = 0;
                int bytesRead =0;
                do
                {
                    bytesRead = reader.Read(buffer, 0, bufferSizeBytes);
                    writer.Write(buffer, 0, bytesRead);
                    offset += bytesRead;
                }
                while (bytesRead == bufferSizeBytes); // while we keep filling our input buffer

                reader.Close();
                fileCnt++;
            }

            writer.Close();
            ProgressUpdate(this, "", 100);
        }
    }
}
